const Gpio = require('onoff').Gpio;

let util = require('util');
let bleno = require('bleno');
let utils = require('./utils');

let BlenoCharacteristic = bleno.Characteristic;

const DEVICE_NAME = 'Tri More';
const APPEARANCE_NAME = 'speed and cascade';
const CENTRALADDRESSRESOLUTION_NAME = 'address resolution supported';

let secondsPerHour = 3600;
let secondsPerMinute = 60;

let kph = 25;
let cadence = 220;
let slope = 3;
let running = false;

let speed = 0;

const light_1 = new Gpio(4, 'in', 'both');
const light_2 = new Gpio(26, 'in', 'both');

let firstPassTime = 0;
let secondPassTime = 0;
light_1.watch((err, value) => {
    let timeArray = process.hrtime();
    if (value == 0) {
        firstPassTime = timeArray[0] * 1000000000 + timeArray[1];
    }

    console.log('receive value from light1');
});

light_2.watch((err, value) => {
    if (value == 0 && firstPassTime != 0) {
        let timeArray = process.hrtime();
        secondPassTime = timeArray[0] * 1000000000 + timeArray[1];

        let costTime = secondPassTime - firstPassTime;
        let costTimeMillSeconds = costTime / 1000000.0;

        speed = (4 / 100) / (costTimeMillSeconds / 1000.0);

        firstPassTime = 0;
        secondPassTime = 0;
    }
    console.log('receive value from light2');
});

function getWriteBuffer() {
    if (speed > 2.7) {
        cadence = 180;
    } else {
        cadence = 160;
    }

    let metersPerSecondRounded = Math.round(speed * 256),
        stepsPerMinute = Math.round(cadence),
        metersPerStep = metersPerSecondRounded / stepsPerMinute,
        metersPerStepRounded = Math.round(metersPerStep * 100);

    let flags = {
        InstantaneousStrideLengthPresent: false,
        TotalDistancePresent: false,
        WalkingOrRunningStatusBits: kph >= 8,
        ReservedForFutureUse1: false,
        ReservedForFutureUse2: false,
        ReservedForFutureUse3: false,
        ReservedForFutureUse4: false,
        ReservedForFutureUse5: false
    };

    return utils
        .bufferHelper()
        .write(8, utils.convertFlags(flags))
        .write(16, metersPerSecondRounded)
        .write(8, stepsPerMinute)
        .finish();
};

function getZzdpWriteBuffer() {
    return utils
        .bufferHelper()
        .write(8, kph)
        .write(8, cadence)
        .write(8, slope)
        .write(8, running ? 1 : 0)
        .finish();
}

// attribute characteristic
let AttributeCharacteristic = function () {
    AttributeCharacteristic.super_.call(this, {
        uuid: '2A05',
        properties: ['indicate'],
        value: null
    });
};

// device characteristic
let DeviceCharacteristic = function () {
    DeviceCharacteristic.super_.call(this, {
        uuid: '2A00',
        properties: ['read'],
        value: null
    });

    this._value = utils.bufferHelper().write(8, DEVICE_NAME).finish();
};

// appearance characteristic
let AppearanceCharacteristic = function () {
    AppearanceCharacteristic.super_.call(this, {
        uuid: '2A01',
        properties: ['read'],
        value: null
    });

    this._value = utils.bufferHelper().write(8, APPEARANCE_NAME).finish();
};

// central address resolution
let CentralAddressResolutionCharacteristic = function () {
    CentralAddressResolutionCharacteristic.super_.call(this, {
        uuid: '2AA6',
        properties: ['read'],
        value: null
    });

    this._value = utils.bufferHelper().write(8, CENTRALADDRESSRESOLUTION_NAME).finish();
};


// RSC Measurement
let RSCMeasurementCharacteristic = function () {
    RSCMeasurementCharacteristic.super_.call(this, {
        uuid: '2A53',
        properties: ['read', 'write', 'notify'],
        value: null
    });

    this._value = new Buffer(0);
    this._updateValueCallback = null;
};

RSCMeasurementCharacteristic.prototype.onReadRequest = function (offset, callback) {
    console.log('read request');
    console.log(callback);
    callback(this.RESULT_SUCCESS, getWriteBuffer());
};

RSCMeasurementCharacteristic.prototype.onWriteRequest = function (data, offset, withoutResponse, callback) {
    console.log('wirte data request');
    console.log(data);
    this._value = data;

    if (this._updateValueCallback) {
        this._updateValueCallback(this._value);
    }
}

RSCMeasurementCharacteristic.prototype.onSubscribe = function (maxValueSize, updateValueCallback) {
    console.log('establish the bridge');
    this._updateValueCallback = updateValueCallback;
    this._updateValueCallback(getWriteBuffer());

    this._intervalId = setInterval(() => {
        this._updateValueCallback(getWriteBuffer());
    }, 500);
};

RSCMeasurementCharacteristic.prototype.onUnsubscribe = function () {
    console.log('remove the subscribe');
    if (this._intervalId) {
        clearInterval(this._intervalId);
        this._intervalId = null;
    }
};

// RSC Feature
let RSCFeatureCharacteristic = function () {
    let feature = Buffer.from('0000000000000000', 'binary');
    RSCFeatureCharacteristic.super_.call(this, {
        uuid: '2A54',
        properties: ['read'],
        value: feature,
        descriptors: []
    });
};

// zzdp controller characteristic
let ZZDPControllerCharacteristic = function () {
    ZZDPControllerCharacteristic.super_.call(this, {
        uuid: 'FFD1',
        properties: ['write'],
        value: null
    });

    this._value = new Buffer(0);
    this._updateValueCallback = null;
};

// 写数据
ZZDPControllerCharacteristic.prototype.onWriteRequest = function (data, offset, withoutResponse, callback) {
    this._value = data;

    try {
        this._updateValueCallback = callback;
        this._updateValueCallback(new Buffer(1));
        let receiveArray = Array.prototype.slice.call(new Int16Array(data));
        console.log(receiveArray);

        kph = receiveArray[0];
        cadence = receiveArray[1];
        slope = receiveArray[2];
        running = receiveArray[3] === 1;

        let obj = {
            kph: kph,
            cadence: cadence,
            slope: slope,
            running: running ? 'True' : 'False'
        }

        if (this.socket !== undefined) {
            this.socket.emit('new-data', obj);
        }

    } catch (err) {
        console.error(err);
    }
}

let ZZDPDataCharacteristic = function () {
    ZZDPDataCharacteristic.super_.call(this, {
        uuid: 'FFD2',
        properties: ['write', 'notify'],
        value: null
    });

    this._value = new Buffer(0);
    this._updateValueCallback = null;
};


ZZDPDataCharacteristic.prototype.onSubscribe = function (maxValueSize, updateValueCallback) {
    this._updateValueCallback = updateValueCallback;
    this._updateValueCallback(getZzdpWriteBuffer());
    console.log('notify the zzdp data');
    this._intervalId = setInterval(() => {
        this._updateValueCallback(getZzdpWriteBuffer());
    }, 500);
}

ZZDPDataCharacteristic.prototype.onUnsubscribe = function () {
    if (this._intervalId) {
        clearInterval(this._intervalId);
        this._intervalId = null;
    }
}

// inherit
util.inherits(AttributeCharacteristic, BlenoCharacteristic);
util.inherits(DeviceCharacteristic, BlenoCharacteristic);
util.inherits(AppearanceCharacteristic, BlenoCharacteristic);
util.inherits(CentralAddressResolutionCharacteristic, BlenoCharacteristic);
util.inherits(RSCMeasurementCharacteristic, BlenoCharacteristic);
util.inherits(RSCFeatureCharacteristic, BlenoCharacteristic);
util.inherits(ZZDPControllerCharacteristic, BlenoCharacteristic);
util.inherits(ZZDPDataCharacteristic, BlenoCharacteristic);

module.exports = {
    AttributeCharacteristic: AttributeCharacteristic,
    DeviceCharacteristic: DeviceCharacteristic,
    AppearanceCharacteristic: AppearanceCharacteristic,
    CentralAddressResolutionCharacteristic: CentralAddressResolutionCharacteristic,
    RSCMeasurementCharacteristic: RSCMeasurementCharacteristic,
    RSCFeatureCharacteristic: RSCFeatureCharacteristic,
    getWriteBuffer: getWriteBuffer,
    ZZDPDataCharacteristic: ZZDPDataCharacteristic,
    ZZDPControllerCharacteristic: ZZDPControllerCharacteristic,
    kph: kph,
    slope: slope,
    cadence: cadence,
    running: running
};

